/* ===================

AX2E (AXESCON XACML 2.0 Engine) is the Java authorization engine, which implements OASIS XACML 2.0 standard.
Copyright (C) 2007 AXESCON LLC

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA


Contact: AXESCON LLC - info{at}axescon.com

 =================== */
package axescon.xacml.api;

import axescon.xacml.model.ctx.XacmlCtx;
import axescon.xacml.model.policy.Expression;
import axescon.xacml.model.shared.attr.BooleanAttribute;

import java.lang.reflect.Array;

/**
 * @author argyn
 * Date: Oct 8, 2005
 *         Responsibilities: implement common functionality of Fn function interface.
 * 
 *
 */
public abstract class AbstractFn implements Fn {

    /**
     * reference to function factory, which can be used if this function's code requires
     * execution of other functions
     */
    private FnFactory fnFactory;

    public FnFactory getFnFactory() {
        return fnFactory;
    }

    public void setFnFactory(FnFactory fnFactory) {
        this.fnFactory = fnFactory;
    }


    public Liquid exec(Expression[] args, CtxHandler ch, XacmlCtx req, ExpressionEvaluator ee) throws
            XacmlException, ExpressionEvalIndeterminateException {
        Liquid[] avs = ee.eval(args, ch, req);
        checkDataTypes(avs);
        return exec(avs);
    }

    public abstract Liquid exec(Liquid[] args) throws XacmlException, FunctionEvalIndeterminateException;


    /**
     * utility method to create boolean result value
     * @param bool booelan value to return as Liquid result
     * @return Liquid object set to bool
     */
    public Liquid returnBoolean(boolean bool) {
        return new Liquid(bool ? BooleanAttribute.TRUE : BooleanAttribute.FALSE);

    }

    /**
     * urn of this function
     */
    private String fnUrn;


    public String getFnUrn() {
        return fnUrn;
    }

    public void setFnUrn(String fnUrn) {
        this.fnUrn = fnUrn;
    }

    /**
     * XACML 2.0 spec mandates strict arg type checking, see ch 7.3. Expression evaluation
     * lines 3334-3337.
     */
    protected Class[] dataTypes;

    public Class[] getDataTypes() {
        return dataTypes;
    }

    public void setDataTypes(Class[] dataTypes) throws XacmlException {
        checkSetDataTypes(dataTypes);
        this.dataTypes = dataTypes;
    }


    /**
     * validates arguments of setDataTypes(dataTypes) function
     * @param dataTypes Java types of this function's arguments
     * @throws XacmlException
     */
    protected void checkSetDataTypes(Class[] dataTypes) throws XacmlException {
        if (getArgTypeListLength() == 0 && dataTypes != null && dataTypes.length != 0)
            throw new XacmlException("too many args",
                    XacmlException.ERR_CODE_CONFIG_FN_WRONG_ARG_LIST);

        if (getArgTypeListLength() > 0 &&
                (dataTypes == null || dataTypes != null && dataTypes.length != getArgTypeListLength()))
            throw new XacmlException("wrong argument list length",
                    XacmlException.ERR_CODE_CONFIG_FN_WRONG_ARG_LIST);
    }


    /**
     * checks data types of arguments at before execution of this function.
     * this method assumes that correct dataTypes were set for a function at initialization.
     *
     * @param args function execution arguments.
     * @throws FunctionEvalIndeterminateException when argument data types are invalid
     */
    public void checkDataTypes(Liquid[] args) throws FunctionEvalIndeterminateException {


        if (args == null||args.length==0)
            if (getMinArgListLength() > 0)
                throw new FunctionEvalIndeterminateException("empty arg list",
                        XacmlException.ERR_CODE_PROCESSING_FN_WRONG_ARG_LIST).setFnUrn(getFnUrn());
            else
                return; // there could be a function with no args, e.g. something like getDate()

        // now we know arg list is not empty

        if (args.length < getMinArgListLength() ||
                (getMaxArgListLength()>0 && args.length > getMaxArgListLength()) )
            throw new FunctionEvalIndeterminateException("num of args: " + args.length
                    + " is out of range.",
                    XacmlException.ERR_CODE_PROCESSING_FN_WRONG_ARG_LIST).setFnUrn(getFnUrn());

        for (int i = 0; i < args.length; i++) {
            assert dataTypes!=null: "null datatypes in fn urn:"+getFnUrn();
            Class dataType=dataTypes[Math.min(i,dataTypes.length-1)];
            if (dataType.isArray()) {//check an array arg
                if (args[i].getValue() != null) {
                    if (!dataType.isInstance(args[i].getValue()))
                        throw new FunctionEvalIndeterminateException("wrong bag arg" + i,
                                XacmlException.ERR_CODE_PROCESSING_FN_WRONG_ARG_TYPE).setFnUrn(getFnUrn());
                }//it's ok to have null instead of a bag (array)

            } else {// check singular arg
                if (args[i] == null || args[i].getValue() == null)//it's not good to send null intead of a single value
                    throw new FunctionEvalIndeterminateException("null arg" + i,
                            XacmlException.ERR_CODE_PROCESSING_FN_EMPTY_ARG).setFnUrn(getFnUrn());
                if (args[i].isBag())
                    throw new FunctionEvalIndeterminateException("bag instead of singular arg" + i,
                            XacmlException.ERR_CODE_PROCESSING_FN_BAG_ARG).setFnUrn(getFnUrn());
                if (!dataType.isInstance(args[i].getValue()))
                    throw new FunctionEvalIndeterminateException("arg" + i,
                            XacmlException.ERR_CODE_PROCESSING_FN_WRONG_ARG_TYPE).setFnUrn(getFnUrn());
            }


        }

    }

    /**
     * function evaluation result data type
     * @return function evaluation result data type, by default it's a last type in
     * argument data type list
     * @throws ExpressionEvalIndeterminateException
     */
    public Class getReturnDataType() throws ExpressionEvalIndeterminateException {
        if (returnDataType == null) returnDataType = dataTypes[dataTypes.length-1];
        return returnDataType;
    }

    /**
     * utility method to set return data type equal to i-th arg data type list
     * class.
     * @param i
     * @return function evaluation result data type set to the i-th arg type
     * argument data type list
     */
    protected Class getReturnDataType(int i) {
        if (returnDataType == null) returnDataType = dataTypes[i];
        return returnDataType;
    }

    /**
     * return Java type of the function evaluation result
     */
    protected Class returnDataType;

    /**
     * utility method to set return data type equal to a bag of i-th arg data type
     * list class.
     * @return function evaluation result data type set to the array of i-th arg type
     * argument data type list
     */
    protected Class getReturnDataTypeBag(int i) {
        if (returnDataType == null) {
            returnDataType = Array.newInstance( dataTypes[i], 0).getClass();
        }
        return returnDataType;
    }

    /**
     * utility method to set return data type equal to a singular boolean attribute.
     * @return boolean function evaluation result
     */
    public Class getBooleanReturnDataType() {
        return BooleanAttribute.class;
    }


    public int getMaxArgListLength() {
        return getMinArgListLength();
    }

    public int getMinArgListLength() {
        return getArgTypeListLength();
    }

}
